/**
 * Copyright (c) 2012 Nokia Corporation. 
 */

package com.nokia.example.tictactoe.engine;

import java.io.UnsupportedEncodingException;

import com.nokia.example.tictactoe.connectivity.Connection;
import com.nokia.example.tictactoe.connectivity.ConnectionManager;
import com.nokia.example.tictactoe.console.Log;

/**
 * The game manager for peer-to-peer game.
 */
public class NetworkGameManager
	extends AbstractGameManager
	implements Connection.Listener
{
	// Members
	protected static AbstractGameManager _instance = null; // Singleton instance
	private GameModel _gameModel = null;
	private ConnectionManager _connectionManager = null;
	
	/** 
	 * @param gameModel
	 * @return The singleton instance of NetworkGameManager.
	 */
	public static AbstractGameManager instance(GameModel gameModel)
	{
		if (_instance == null) {
			_instance = new NetworkGameManager(gameModel);
		}
		
		return _instance;
	}
	
	/**
	 * Releases the singleton instance.
	 */
	public void release() {
		_instance = null;
	}
	
	/**
	 * Constructor.
	 * @param gameModel
	 */
	private NetworkGameManager(GameModel gameModel) {
		if (gameModel == null) {
			throw new IllegalArgumentException();
		}
		
		_gameModel = gameModel;
		_connectionManager = ConnectionManager.instance(this);
	}
	
	/**
	 * From Connection.Listener.
	 */
	public void onStateChanged(int state) {
		switch (state) {
		case Connection.CONNECTED: {
			_gameModel.initModel();
			
			if (_connectionManager.isServer()) {
				_gameModel.setPlayerMark(GameModel.MARK_EMPTY);	
				_gameModel.setWhoseTurn(GameModel.MARK_EMPTY);
				Log.addEntry("Connected as server");
			}
			else {
				_gameModel.setPlayerMark(GameModel.MARK_X);
				_gameModel.setWhoseTurn(GameModel.MARK_X);
				Log.addEntry("Connected as client");
			}
			
			break;
		}
		case Connection.NOT_CONNECTED: {
			// Disconnected
			_gameModel.setPlayerMark(GameModel.MARK_EMPTY);
			Log.addEntry("Disconnected");
			break;
		}
		}
	}

	/**
	 * From Connection.Listener.
	 */
	public void onPeerDisconnected() {
		
	}
	
	/**
	 * From Connection.Listener.
	 */
	public void onMessageSent(byte[] message) {
		try {
			Log.addEntry("Sent: " + new String(message, "UTF8"));
		}
		catch (UnsupportedEncodingException e) {}
	}
	
	/**
	 * From Connection.Listener.
	 */	
	public void onReceived(byte[] message) {
		if (interpretAndExecuteMessage(message) == false) {
			System.out.println("NetworkGameManager::onReceived(): interpretAndExecuteMessage() failed!");
		}
	}
	
	/**
	 * From Connection.Listener.
	 */
	public void onTimeout() {}
	
	/**
	 * From Connection.Listener.
	 */
	public void onError(String errorMessage) {
		Log.addEntry(errorMessage);
	}

	/**
	 * From AbstractGameManager.
	 * Starts looking for other players.
	 */
	public void startGame() {
		System.out.println("NetworkGameManager::startGame()");
		_connectionManager.connect();
	}
	
	/**
	 * From AbstractGameManager.
	 */
	public void endGame() {
		onGameOver();
	}
	
	/** 
	 * From AbstractGameManager.
	 */
	public final boolean gameOngoing() {
		return (_connectionManager.state() == ConnectionManager.CONNECTED);
	}
	
	/**
	 * From AbstractGameManager.
	 */
	public void onSquarePressed(int x, int y) {
		System.out.println("NetworkGameManager::onSquarePressed(): ["
				+ x + ", " + y + "]");
		int playerMark = _gameModel.playerMark();
		
		if (playerMark == GameModel.MARK_EMPTY
				|| playerMark != _gameModel.whoseTurn()
				|| !gameOngoing())
		{
			System.out.println("NetworkGameManager::onSquarePressed(): Not your turn!");
			return;
		}
		
		try {
			if (_gameModel.markAt(x, y) == GameModel.MARK_EMPTY) {
				_connectionManager.send(constructMessage(x, y, playerMark));
				_gameModel.setMark(x, y, playerMark);
				_gameModel.switchTurns();
			}
		}
		catch (IllegalArgumentException e) {}
	}
	
	/**
	 * From AbstractGameManager.
	 * Disconnects.
	 */
	public void onGameOver() {
		Log.addEntry("Game over, disconnecting...");
		_connectionManager.disconnect();
	}
	
	/**
	 * From AbstractGameManager.
	 */
	public int type() {
		return AbstractGameManager.NETWORK;
	}
	
	/**
	 * Returns the character matching the given mark.
	 * @param mark The mark to convert.
	 * @return The character matching the mark.
	 */
	private char markToChar(int mark) {
		if (mark == GameModel.MARK_X) {
			return 'X';
		}
		
		if (mark == GameModel.MARK_O) {
			return 'O';
		}
		
		return '\0';
	}
	
	/**
	 * Constructs a message to send to the opponent.
	 * @param x The X coordinate where the mark was added.
	 * @param y The Y coordinate where the mark was added.
	 * @param mark Tells which mark (X or O) was added.
	 * @return The message to send.
	 */
	private byte[] constructMessage(int x, int y, int mark) {
		StringBuffer buffer = new StringBuffer();
		
		if (_connectionManager.isServer()) {
			// A typical response: "tb_11", where row 1, col 1 has been marked
			// by the local user
			buffer.append("tb_");
			buffer.append(y);
			buffer.append(x);
		}
		else {
			// Here's a typical message sent by the client:
			// "O|tb_00*|tb_01*|tb_02*X|tb_10*|tb_11*|tb_12*|tb_20*|tb_21*|tb_22*<EOF>"
			// where row 0, col 2 has an X mark.
			int opponentsMark = GameModel.MARK_X;
			
			if (_gameModel.playerMark() == GameModel.MARK_X) {
				opponentsMark = GameModel.MARK_O;
			}
			
			// Client tells the server its mark
			buffer.append(markToChar(opponentsMark));
			
			final int gridSizeSquared = _gameModel.gridSizeSquared();
			int tempMark;
			
			for (int i = 0; i < gridSizeSquared; ++i) {
				for (int j = 0; j < gridSizeSquared; ++j) {
					buffer.append("|tb_");
					buffer.append(i);
					buffer.append(j);
					buffer.append("*");
					
					if (x == j && y == i) {
						tempMark = mark;
					}
					else {
						tempMark = _gameModel.grid()[i * gridSizeSquared + j];
					}
					
					if (tempMark != GameModel.MARK_EMPTY) {
						buffer.append(markToChar(tempMark));
					}
				}
			}
		}
		
		System.out.println("NetworkGameManager::constructMessage(): " + buffer.toString());
		return buffer.toString().getBytes();
	}
	
	/**
	 * Parses the given messages and modifies the game state accordingly.
	 * @param message The message to interpret.
	 * @return Returns true if successful, false otherwise.
	 */
	private boolean interpretAndExecuteMessage(final byte[] message) {
		String msg = null;
		
		try {
			msg = new String(message, "UTF8");
		}
		catch (UnsupportedEncodingException e1) {
			msg = new String(message);
		}
		
		Log.addEntry("Rv: " + msg);
		
		System.out.println("NetworkGameManager::interpretAndExecuteMessage(): " + msg);
		boolean messageOk = false;
		
		if (_connectionManager.isServer()) {
			// Here's a typical message sent by the client:
			// "O|tb_00*|tb_01*|tb_02*X|tb_10*|tb_11*|tb_12*|tb_20*|tb_21*|tb_22*<EOF>"
			// where row 0, col 2 has an X mark.

			if (msg.length() >= _gameModel.gridSizeSquared() * 7 + 1) {
				if (_gameModel.playerMark() == GameModel.MARK_EMPTY) {
					// This is the first message of the game, get my mark
					if (msg.charAt(0) == 'X') {
						_gameModel.setPlayerMark(GameModel.MARK_X);
						_gameModel.setWhoseTurn(GameModel.MARK_O);
					}
					else {
						_gameModel.setPlayerMark(GameModel.MARK_O);
						_gameModel.setWhoseTurn(GameModel.MARK_X);
					}
					
					Log.addEntry("My mark is now "
							+ markToChar(_gameModel.playerMark()));
				}
				
				final int length = msg.length();
				int x = -1;
				int y = -1;
				char prevChar;
				char tempChar;
				
				for (int i = 5; i < length; ++i) {
					prevChar = msg.charAt(i - 1);
					
					if (prevChar == '_') {
						try {
							y = Integer.parseInt(msg.substring(i, i + 1));
						}
						catch (Exception e) {
							Log.addEntry("NetworkGameManager::interpretAndExecuteMessage(): "
									+ e.toString());
						}
					}
					else if (prevChar == '*') {
						tempChar = msg.charAt(i);
						
						if (tempChar == 'X'
							&& _gameModel.markAt(x, y) == GameModel.MARK_EMPTY)
						{
							_gameModel.setMark(x, y, GameModel.MARK_X);
						}
						else if (tempChar == 'O'
								 && _gameModel.markAt(x, y) == GameModel.MARK_EMPTY)
						{
							_gameModel.setMark(x, y, GameModel.MARK_O);
						}
						
						x = -1;
						y = -1;
					}
					else if (y >= 0 && x == -1) {
						try {
							x = Integer.parseInt(msg.substring(i, i + 1));
						}
						catch (Exception e) {
							Log.addEntry("NetworkGameManager::interpretAndExecuteMessage(): "
									+ e.toString());
						}
					}
				}
				
				messageOk = true;
			}
		}
		else {
			// A typical response: "tb_11", where row 1, col 1 has been marked
			// by the local user
			if (msg.length() >= 5) {
				int opponentsMark = GameModel.MARK_X;
				
				if (_gameModel.playerMark() == GameModel.MARK_X) {
					opponentsMark = GameModel.MARK_O;
				}
				
				try {
					_gameModel.setMark(Integer.parseInt(msg.substring(4, 5)),
									   Integer.parseInt(msg.substring(3, 4)),
									   opponentsMark);
					messageOk = true;
				}
				catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		
		_gameModel.switchTurns();
		Log.addEntry(markToChar(_gameModel.whoseTurn()) + "'s turn");
		return messageOk;
	}
}
